#include"process.h"
#include"thread.h"
#include"string.h"
#include"global.h"
#include"debug.h"
#include"memory.h"
#include"file.h"

static void process_start(void*);
//retrun from kernal interrupt goto DPL3 user process
extern void intr_exit(void);


bool process_virtual_pool_init(task_tcb* tcb){
	//how many page we used to manage user process heap
	uint32_t mem_bits_pg_cnt=div_round_up((PROC_TOP_ADDR-PG_SIZE-PROC_HEAP_STRAT)/PG_SIZE/8,PG_SIZE);
	void* vaddr=get_page(PG_K,mem_bits_pg_cnt);
	if(!vaddr){
		return false;
	}
	tcb->process_vaddr_pool.vaddr_start=PROC_HEAP_STRAT;
	tcb->process_vaddr_pool.vaddr_bitmap.bits=(uint8_t*)vaddr;
	tcb->process_vaddr_pool.vaddr_bitmap.bmap_bytes_len=mem_bits_pg_cnt*PG_SIZE;
	mem_bank_init(&tcb->process_mm_bank);
	return true;
}

void process_execute(void* file_name,const char* name){
	thread_des process_main_des;
	process_main_des.func=(thread_func)process_start;
	process_main_des.name=name;
	process_main_des.args=file_name;
	process_main_des.priority=30;
	task_tcb* tcb=(task_tcb*)get_page(PG_K,1);
	if(!process_virtual_pool_init(tcb)){
		return;
	}
	if(!alloc_process_pagedir(tcb)){
		return;
	}
	thread_init(&process_main_des,tcb);
	thread_create(&process_main_des,tcb);
	ASSERT(tcb->page_dir!=NULL);
	ASSERT(file_name!=NULL && name!=NULL);
}

//will be call by kthread when excete here the context is in process context
static void process_start(void* user_process){
	task_tcb* curr=current_thread();
	intr_stack* pintr_stack=(intr_stack*)((uint32_t)curr+PG_SIZE-sizeof(intr_stack));
	pintr_stack->ds=pintr_stack->es=U_DATA_SEL;
	pintr_stack->eip=(void (*)(void))user_process;
	pintr_stack->cs=U_CODE_SEL;
	//enable interrupt when interrupt return
	pintr_stack->eflags=EF_MBS | EF_IF ;
	pintr_stack->ss=U_STACK_SEL;
	//here we set user procss stack 
	void* stack_page=get_fix_vpage(PG_U,PROC_TOP_ADDR-PG_SIZE);
	ASSERT(stack_page!=NULL);
	pintr_stack->esp=(uint32_t)stack_page+PG_SIZE;

	//interrupt return user process will start cpu will run as user mode now
	//!!do not call intr_exit use call cpu will push return address to stack
	asm volatile("movl %0,%%esp;jmp intr_exit"::"r"(pintr_stack));
}

bool alloc_process_pagedir(task_tcb* tcb){
	tcb->page_dir=get_page(PG_K,1);
	if(tcb->page_dir!=NULL){
		//copy kernal page dir entry
		memcpy((void*)((uint32_t)tcb->page_dir+0x300*4),(void*)(0xfffff000+0x300*4),256*4);
		//make last page dir entry point to self
		*(uint32_t*)((uint32_t)tcb->page_dir+1023*4)=((uint32_t)vaddr2paddr((uint32_t)tcb->page_dir) | PG_P_1 | PG_RW_W | PG_US_U);
		return true;
	}
	return false;
}

#define USER_PDE_CNT 768
#define PAGE_PTE_CNT 1024

void process_resource_release(task_tcb* task){
		
	uint32_t pde_idx=0;
	uint32_t pte_idx=0;
	uint32_t* page_dir=task->page_dir;
	uint32_t* pde_vaddr=NULL;
	uint32_t* pte_vaddr=NULL;
	uint32_t* pte_base=NULL;
	uint32_t fd_idx;
	uint32_t pde_paddr;
	uint32_t pte_paddr;

	while(pde_idx<USER_PDE_CNT){
		pde_vaddr=page_dir+pde_idx;
		pde_paddr=*pde_vaddr;
		if(pde_paddr & 0x01){
			pte_idx=0;
			//we should use virtual address to assecss pte
			pte_base=pte_ptr(0x400000*pde_idx);
			while(pte_idx<PAGE_PTE_CNT){
				pte_vaddr=pte_base+pte_idx;
				pte_paddr=*pte_vaddr;
				if(pte_paddr & 0x01){
					pfree(PG_K,pte_paddr & 0xfffff000,1);
				}
				pte_idx++;
			}
			pfree(PG_K,pde_paddr & 0xfffff000,1);
		}
		pde_idx++;
	}
	
	free_page(PG_K\
		,task->process_vaddr_pool.vaddr_bitmap.bits\
		,task->process_vaddr_pool.vaddr_bitmap.bmap_bytes_len/PG_SIZE);
	
	for(fd_idx=3;fd_idx<TASK_MAX_FD_CNT;fd_idx++){
		if(task->fd_table[fd_idx]!=-1){
			sys_close(task->fd_table[fd_idx]);
		}
	}
}
